#include"tcp.hpp"
#include"http.hpp"//for URL
#include<stdexcept>

//tcpwraper for general using ...
using namespace std;

TcpHost::TcpHost():ai(0)
{
}

TcpHost::TcpHost(const TcpHost &r) {
  sethost(r.host, r.port);
}

TcpHost & TcpHost::operator=(const TcpHost &r) {
  if (&r == this) return *this;
  sethost(r.host, r.port);
  return *this;
}

int TcpHost::sethost(const string&h, int p)
{
  host = h;
  port = p;
  if (ai) {
    ::freeaddrinfo(ai);
  }
  ai = 0;//force 0 here
  struct addrinfo hints;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;//TODO: implement the part IPV6
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = 0;
  hints.ai_protocol = 0;
  char sport[12];
  snprintf(sport, sizeof(sport), "%d", p);
  int r = ::getaddrinfo(h.c_str(), sport, &hints, &ai);
  return r;
}

TcpHost::TcpHost(const string& h, int p): ai(0){
  int r=sethost(h, p);
  if (r!=0) port = -1;//can i use this to indicate the error
}

TcpHost::~TcpHost()
{
  if (ai)
    ::freeaddrinfo(ai);
}

int TcpAgent::is_epipe() {
  if (errno == EPIPE) {
    return 1;
  }
  int valopt=0;
  socklen_t slen = sizeof(int);
  int r = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&valopt, &slen);
  int rt;
  if (r==-1) {
    rt = (errno==EBADF || errno ==ENOTSOCK);
  } else     rt = (valopt == EPIPE);
  return rt;
}

int TcpAgent::read(void *buf, int len) {
  if (len<=0) return 0;
  int r = 0;
  r =  ::recv(fd, (char*) buf, len, 0);
  return r;
}

int TcpAgent::read_all(string& str) {
  char buf[4096];
  int r, sum=0;
  while ( (r=read(buf, sizeof(buf))) >0) {
    str += string(buf, r);
    sum +=r;
  }
  if (r<0 && errno!= EAGAIN) {
    return r;
  }
  return sum;
}

int TcpAgent::write(const void *buf, int len) {
  int r = 0;
  r = ::send(fd, (const char*)buf, len, MSG_NOSIGNAL);
  return r;
}

int TcpAgent::write(const string& str) {
  return write(str.c_str(), str.size());
}
  
int TcpAgent::write_all(string& str) {
  int r = write_all(str.c_str(), str.size());
  str=string(str.end()-r, str.end());
  return r;
}

//return unwrite len
int TcpAgent::write_all(const void*buf, int len) {
  if (len <=0) return 0;
  int r, lft = len;
  do {
    r = write(buf, lft);
    if (r == -1)
      break;
    lft-=r;
    buf = (char*)buf + r;
  } while(lft);
  return lft;
}

void TcpAgent::close(int s)
{
  if (s>=0) {
#ifdef WIN32
    ::closesocket(fd);
#else
    ::close(fd);
#endif
  }
}

void TcpAgent::close() {
  if (fd>=0) {
    close(fd);
    fd = -1;
  }
}

void TcpAgent::closewrite() {
  if (fd>=0) {
    shutdown(fd, SHUT_WR);
  }
}

void TcpAgent::closeread() {
  if (fd>=0) {
    shutdown(fd, SHUT_RD);
  }
}

int TcpClient::connect(const TcpHost& tohost)
{
  if (fd>=0) {
    if (tohostname ==tohost.host && toport == tohost.port)
      return 1;
    else
      close();
  }
  int s;
  struct addrinfo *rp;
  for (rp=tohost.ai; rp!=0; rp=rp->ai_next) {
    s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if (s == -1) continue;
    if (do_connect(s, rp->ai_addr, rp->ai_addrlen)>=0)
      break;
    close(s);
  }
  if (rp == 0) {
    throw runtime_error(string("could not connect to ") + tohost.host + ", "+strerror(errno));
    return -10;
  }
  fd = s;
  // set sockopt
  struct timeval tv;
  socklen_t slen =sizeof(tv);
  tv.tv_sec = cnttimeout/1000;
  tv.tv_usec = (cnttimeout%1000)*1000;
  setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, slen);
  setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, slen);
  return 0;
}

int TcpClient::do_connect(int socket, const struct sockaddr *address, socklen_t address_len)
{
  int r = -1;
#ifndef WIN32  
  long oflags = fcntl(socket, F_GETFL);
  long nflags = (oflags|O_NONBLOCK);
  fcntl(socket, F_SETFL, nflags);
  int res = ::connect(socket, address, address_len);
  if (res <0) {
    if (errno == EINPROGRESS) {
      int valopt;
      socklen_t slen = sizeof(int);
      timeval tv;
      fd_set fs;
      tv.tv_sec = cnttimeout/1000;
      tv.tv_usec = (cnttimeout%1000)*1000;
      FD_ZERO(&fs);
      FD_SET(socket, &fs);
      if (select(socket+1, NULL, &fs, NULL, &tv) >0) {
        getsockopt(socket, SOL_SOCKET, SO_ERROR, &valopt, &slen);
        if (valopt) {
          r =  -1;//error
        } else r = 1;//success
      } else {
        r = -2; //timeout
      }
    } else {
      r = -3;//st+=string("error connecting:") + strerror(errno);
    }      
  } else {
    r = 1;//success
  }
  fcntl(socket, F_SETFL, oflags);
#else
  r = ::connect(socket, address, address_len);
  if (r>=0) r = 1;
#endif
  return r;
}

int TcpClient::reconnect(const TcpHost& tohost)
{
  close();
  return connect(tohost);
}


/*
  changelog 2007-11-06: use getaddrinfo instead of nothread-safe gethostbyname
  changelog 2007-11-07: use SO_RCVTIMEOU and SO_SNDTIMEO instead of select





*/
